Skip to content

Jaguar2 5/10 MHz narrowband — 8821C validated, 8822B gated#216

Merged
josephnef merged 3 commits into
masterfrom
jaguar2-narrowband
Jul 8, 2026
Merged

Jaguar2 5/10 MHz narrowband — 8821C validated, 8822B gated#216
josephnef merged 3 commits into
masterfrom
jaguar2-narrowband

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

What

PR #215 left 5/10 MHz narrowband as Jaguar3-only. Investigating the in-repo reference drivers showed Jaguar2 has real vendor narrowband support (config_phydm_switch_bandwidth_8822b/_8821c) — same "RF stays in 20 MHz mode, underclock the baseband" concept as Jaguar3, packed into the BB 0x8ac clock word (small-BW [7:6] + ADC/DAC fields, 0x8c4[30]=0, 0x8c8[31]=1) instead of Jaguar3's 0x9b0/0x9b4 dividers. Jaguar1 has no vendor narrowband anywhere (enum-only dead code in every rtl8812au tree) — documented out of scope.

The three pieces the vendor registers alone don't give (hardware-bisected)

  1. BB reset after the re-clock — MAC 0x0 BIT16 toggle (the _iqk_bb_reset mechanism) so the DAC/DFE relatch at the new sample rate; the Jaguar3 port learned the same lesson.
  2. NB as an end-of-bring-up retune — the vendor NB switch only ever runs on an initialized interface (iw/OpenHD monitor_chan_override); re-clocking mid-bring-up before IQK leaves the chip deaf both directions.
  3. The config_phydm_switch_band CCK-block trio (5G: 0x808[28]=0, 0x454[7]=1, 0xa80[18]=1) — the 8821C path had it, the 8822B path never ported it; a CCK engine left enabled under the NB re-clock breaks the OFDM demod.

Fixed en route: 8821C RX-DFIR 0xc20[31] was wrong for bw 5/6, and apply_tx_power read raw ChannelWidth 5/6 as HT40 and missed the regulatory txpwr_lmt lookup entirely (unclamped 63) — narrowband now folds to the 20 MHz power column like Jaguar3.

Hardware validation

8821C (RTL8811CU / RTL8821CU / RTL8821CE) — fully working:

  • SDR occupied bandwidth (c811, differential PSD): 16.71 / 8.55 / 4.41 MHz → ratios 1.95 (10M) and 3.79 (5M)
  • Cross-generation vs Jaguar3 c812, both directions, both widths, at 20 MHz-control parity (10M: 44–58 hits, 5M: 56–106 vs control 57–67)
  • NB survives FastRetune (175 hits across sweep dwells — the re-clock state is bandwidth-keyed and the NB RF18 equals the 20 MHz encoding)
  • 2.4 GHz + efuse/regulatory power path verified (134 hits ch6, no flat override)
  • 20/40/80 regression smokes pass on both variants; ctest 13/13

8822B (RTL8822BU) — gated off (narrowband_ok=false, bw_mask clears 5/10): with the same recipe it syncs ~10% of NB frames and airs no NB TX, while the OpenHD kernel module decodes 5000/5000 of our Jaguar3 NB frames on the same dongle with a byte-identical firmware blob and identical page-8 BB state (proven by a write_reg reverse-bisect on the working kernel). The kernel's own 20→10 MHz self-diff leaves exactly one unexplained delta: an RF-internal RXBB LPF retune (RF 0x82/0xf2) driven by a runtime FW interaction devourer doesn't reproduce yet — documented in the NB branch comment, with DEVOURER_NB_ADC/DEVOURER_NB_DAC divider-override knobs left in as the debugging levers for that follow-up.

Tests

  • tests/jaguar2_narrowband_sdr.sh — differential-PSD occupied-BW check (Jaguar3 script's twin, CHIP=8821c default)
  • tests/narrowband_cross_rx.sh — both-ends-re-clocked demod proof across generations (20M control + 10/5 MHz cells, DLFW-flake retry)

🤖 Generated with Claude Code

josephnef and others added 3 commits July 8, 2026 12:43
Port the vendor narrowband re-clock (config_phydm_switch_bandwidth_8822b/
_8821c: 0x8ac small-BW [7:6] + ADC/DAC clock fields, 0x8c4[30]=0,
0x8c8[31]=1, RF18 kept at the 20 MHz encoding) to the Jaguar2 HAL, plus the
three pieces the vendor register recipe alone does not give, each
hardware-bisected on the bench:

- BB reset after the re-clock (MAC 0x0 BIT16 toggle) so the DAC/DFE relatch
  at the new sample rate — same lesson the Jaguar3 port hardware-taught.
- Narrowband applied as an END-of-bring-up retune (kernel-flow parity): the
  vendor NB switch only ever runs on an initialized interface; re-clocking
  mid-bring-up (before IQK / TRX re-assert) leaves the chip deaf.
- The config_phydm_switch_band CCK-block trio (5G: 0x808[28]=0, 0x454[7]=1,
  0xa80[18]=1) the 8822B path had never ported — a CCK engine left enabled
  under the NB re-clock breaks the OFDM demod.

Also fixed en route: the 8821C RX-DFIR 0xc20[31] conditional was wrong for
bw=5/6, and apply_tx_power read raw ChannelWidth 5/6 as HT40 and missed the
txpwr_lmt lookup entirely (unclamped 63) — narrowband now folds to the
20 MHz power column, mirroring Jaguar3.

8821C (RTL8811CU/8821CU/8821CE) is fully validated: SDR occupied-bandwidth
16.71/8.55/4.41 MHz (ratios 1.95 / 3.79), cross-generation RX+TX against
Jaguar3 at 20 MHz-control parity for both widths, NB survives FastRetune,
2.4 GHz + efuse-power fold path verified.

8822B stays gated (narrowband_ok=false, bw_mask clears kBw5|kBw10): with
the same recipe it syncs ~10% of NB frames and airs no NB TX, while the
OpenHD kernel module decodes 5000/5000 on the same dongle with a
byte-identical firmware blob and identical page-8 BB state (write_reg
reverse-bisect). The kernel's own 20->10 MHz delta shows an RF-internal
RXBB LPF retune (RF 0x82/0xf2) driven by a runtime FW interaction devourer
does not reproduce yet — documented in the NB branch comment; the
DEVOURER_NB_ADC/DEVOURER_NB_DAC divider-override knobs remain as the
debugging levers for that follow-up.

Jaguar1 has no vendor narrowband implementation anywhere (enum-only dead
code) — documented out of scope.

New tests: tests/jaguar2_narrowband_sdr.sh (differential-PSD occupied-BW,
the Jaguar3 script's twin) and tests/narrowband_cross_rx.sh (both-ends
re-clocked demod proof across generations, with DLFW-flake retry).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… lever, document 5 MHz CFO limit

Systematic comparison against the vendored OpenHD rtl88x2bu (issue #184
pattern — devourer as an incomplete port):

- tests/narrowband_cross_rx.sh counted rx.txhit EVENTS, but the demo
  throttles them (first 10, then every 100th) — cells reading "57 hits"
  were really ~4700 frames. Count the `hits` field of the last event.
  Re-validated with true numbers: 8821C 10 MHz at 2100 vs 4700-frame
  20 MHz control (the remaining factor is NB frame airtime), 5 MHz at
  7000+ per 10 s on 2.4 GHz.

- Time-resolved kernel dumps show the working driver CONVERGES RF
  0x82/0xf2 (RXBB) to narrowband values over ~3 s after the switch
  (0x44 -> 0x50 -> 0x52) via its dynamic loop. Forcing those converged
  values statically from devourer's NB branch does NOT recover 8822B RX
  (hardware-tested, reverted) — the 8822B gap note now records the
  refutation; the operative delta is the dynamic mechanism itself or a
  still-unobserved FW interplay.

- 5 MHz at 5 GHz is CFO-limited physics, not a driver bug: quarter clock
  shrinks subcarrier spacing 4x, and a far-offset TX/RX crystal pair is
  bimodal per bring-up (9100 hits one run, 0 the next, while a
  closer-crystal peer catches 10200 from the same TX); the same pair is
  stable at 2.4 GHz. Documented in the test header + CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
usbmon capture of the working kernel's 20->10 MHz switch + the 5 s
convergence window: 189 vendor control writes, zero H2C box writes, and
only three 3-wire RF writes (RF18 x2 for the switch, RF 0x42 thermal
trigger from the 2.1 s watchdog). The switch-time MAC writes are the
halmac cfg_bw defaults (0x668 + the 80 MHz MAC-clock trio 0x24/0x55c/
0x638), spur/CSI block, and DIG — all benign or already matched.

Meaning: the kernel's RF 0x82/0xf2 RXBB retune at narrowband is done by
the on-chip firmware autonomously, with no host trigger — and the FW
does it under the kernel but not under devourer, whose FW never receives
the interface-up H2C state (media status / macid / RA) that arms the FW
dynamic engine. Since statically forcing the converged RF values does
not recover RX, the FW's runtime assist itself is the operative delta.
Comment updated to record the closed hypothesis space; porting the
interface-up H2C bring-up is the follow-up (same machinery gap as the
issue #184 thermal-tracking request).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef josephnef merged commit e1896ab into master Jul 8, 2026
15 checks passed
@josephnef josephnef deleted the jaguar2-narrowband branch July 8, 2026 10:22
josephnef added a commit that referenced this pull request Jul 8, 2026
…r2 variants) + kernel-parity ports (#219)

## What

PR #216 shipped Jaguar2 5/10 MHz narrowband but **gated the 8822B** — it
synced ~0.5% of NB frames on RX and aired nothing on TX, while the
OpenHD kernel module did full-rate NB on the same dongle with the same
firmware. This PR finds and fixes the root cause, and lands the
vendor-parity gaps discovered along the way.

## Root cause: RF18 re-latch edge

**The RTL8822B RF synthesizer only re-latches its internal channel state
on an RF18 (`RF 0x18`) *value edge*** — a same-value rewrite is a no-op.
Devourer applies narrowband as an end-of-bring-up re-clock and rewrote
RF18 with the *identical* value, leaving the RF latched to its
pre-re-clock internals: RX deaf, TX invalid, band-independent. The
kernel driver never exposes this because its flow always tunes *through*
a channel/band change (an RF18 edge) around any bandwidth switch.

This also retroactively explains the earlier "firmware-autonomous RF
`0x82`/`0xf2` retune" observation — those are RF-internal LUT outputs
refreshed by the RF18 edge, not FW action.

**Fix** (`set_channel_bw`, 8822B NB branch): write RF18 once with a
substitute channel byte, then the real value, on both RF paths.

## How it was found — golden-init replay

Every readable-state hypothesis had already been eliminated by A/B
against the vendored kernel module (a 116-register MAC+BB flip and
removal of the module's only H2C traffic both left kernel NB working).
The latch is invisible to state diffing because it keys on the *write
event*, not the value. So:

1. usbmon-capture the working kernel's complete post-DLFW register write
stream (3630 writes incl. the 10 MHz switch).
2. Replay it verbatim from devourer via a new `DEVOURER_REPLAY_WSEQ`
debug knob (applied at end of `Init`, overriding devourer's own config)
→ NB unlocks (39100 hits vs ~10) → causal state is host-writable.
3. Delta-debug the stream (~25 automated probes) down to the causal RF18
write pair. Edge pair alone: 23300 hits; same-value rewrite alone: 9.

`DEVOURER_REPLAY_WSEQ` is kept as a permanent lever — it converts any
"same registers, different behavior" mystery into a mechanical
bisection.

## Hardware validation (T3U 8822BU ↔ Jaguar3 8812CU, true frame counts)

- ch44 10 MHz RX 7700 / TX 12600; ch44 5 MHz RX 6500; 2.4 GHz ch6 10 MHz
RX 7700
- scripted `tests/narrowband_cross_rx.sh`: **20M=3900 / 10M=4000 /
5M=3900** — full control parity, both widths
- 20/40/80 smokes + `ctest` pass; the RF18 edge is narrowband-gated
(wide paths byte-unchanged)

Caps ungated: `narrowband_ok=true` on both Jaguar2 variants, `bw_mask`
keeps 5/10.

## Also in this branch (kernel-parity ports found by the same
comparison)

These were prerequisites/gap-closures uncovered while diffing devourer's
init against the kernel's write stream. Each is independently
vendor-parity and measured; together they roughly **doubled 8822B 20 MHz
RX** (~4700 → ~8100 hits/cell) before the NB fix:

- **halrf kfree** — efuse power/PA-bias trim: the PA-bias RF LUT write
(RF `0x51/0x52` → `0x3f` via the `0xef[10]` window) + per-channel-group
TX gain trim (`RF 0xde/0x65/0x55`). Gotcha ported with it: close the RF
LUT window before the `0x51/0x52` reads (the table apply leaves it open,
aliasing the reads).
- **eFEM CCA table fix** — RFE types 3/5/12/15/16/17/19 (external-FEM
boards incl. the bench T3U) need `cca_ifem_ccut_rfe`; devourer hardcoded
the plain iFEM column for every board. Plus the 20 MHz DFS-channel CCA
adjust.
- **phydm spur calibration** — reset the NBI/CSI notch state on every
channel set, and PSD-detect + notch the chip's own clock spur on the
fixed spur channels (validated engaging at ch6/2440 MHz).
- **halmac cfg_mac_clk** — the MAC µs-tick dividers (`0x55c/0x638`);
chip reset defaults are wrong for the running clock.
- **SoML enable + RxHP band block**, and the **FW
GENERAL_INFO/PHYDM_INFO H2C** (`send_fw_general_info`) — the kernel's
post-DLFW FW identity handshake.

## Test notes

- `DEVOURER_REPLAY_WSEQ=<file>` — golden-init replay (lines: `<addr_hex>
<width 1|2|4> <val_hex>`).
- `tests/narrowband_cross_rx.sh` / `tests/jaguar2_narrowband_sdr.sh` now
cover both Jaguar2 variants.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant